// ============================================================================
// File: pkg/lazy/loader.go - v2.0 Enhanced
// ============================================================================
package lazy

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"unsafe"

	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
	"gitee.com/wangtsingx/onyx-snapshotter/pkg/cache"
	"gitee.com/wangtsingx/onyx-snapshotter/pkg/content"
	"gitee.com/wangtsingx/onyx-snapshotter/pkg/puller"
)

type Loader struct {
	root      string
	cs        *content.Store
	puller    *puller.Puller
	cache     *cache.LRUCache
	fd        int
	stop      chan struct{}
	wg        sync.WaitGroup
	watching  map[string]bool
	watchMu   sync.Mutex
	pending   map[string]*sync.Mutex
	pendingMu sync.Mutex
}

func NewLoader(root string, cs *content.Store, p *puller.Puller, cache *cache.LRUCache) (*Loader, error) {
	return &Loader{
		root:     root,
		cs:       cs,
		puller:   p,
		cache:    cache,
		stop:     make(chan struct{}),
		watching: make(map[string]bool),
		pending:  make(map[string]*sync.Mutex),
	}, nil
}

func (l *Loader) Start(ctx context.Context) error {
	logrus.Info("starting lazy loader with fanotify")

	// Initialize fanotify
	fd, err := unix.FanotifyInit(
		unix.FAN_CLASS_CONTENT|unix.FAN_UNLIMITED_QUEUE|unix.FAN_UNLIMITED_MARKS,
		unix.O_RDONLY|unix.O_LARGEFILE,
	)
	if err != nil {
		return fmt.Errorf("failed to init fanotify: %w", err)
	}
	l.fd = fd
	defer unix.Close(fd)

	// Watch snapshots directory
	snapshotsDir := filepath.Join(l.root, "snapshots")
	if err := l.addWatch(snapshotsDir); err != nil {
		return fmt.Errorf("failed to add watch: %w", err)
	}

	logrus.Infof("watching directory: %s", snapshotsDir)

	// Event loop
	l.wg.Add(1)
	go l.eventLoop(ctx)

	<-l.stop
	return nil
}

func (l *Loader) Stop() {
	logrus.Info("stopping lazy loader")
	close(l.stop)
	l.wg.Wait()
}

func (l *Loader) addWatch(path string) error {
	l.watchMu.Lock()
	defer l.watchMu.Unlock()

	if l.watching[path] {
		return nil
	}

	err := unix.FanotifyMark(
		l.fd,
		unix.FAN_MARK_ADD|unix.FAN_MARK_MOUNT,
		unix.FAN_OPEN_PERM|unix.FAN_ACCESS_PERM,
		unix.AT_FDCWD,
		path,
	)
	if err != nil {
		return err
	}

	l.watching[path] = true
	return nil
}

func (l *Loader) eventLoop(ctx context.Context) {
	defer l.wg.Done()

	buf := make([]byte, 8192)
	for {
		select {
		case <-l.stop:
			return
		default:
		}

		n, err := unix.Read(l.fd, buf)
		if err != nil {
			if err == unix.EINTR {
				continue
			}
			logrus.WithError(err).Error("failed to read fanotify event")
			return
		}

		if n == 0 {
			continue
		}

		// Parse events
		offset := 0
		for offset < n {
			event := (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[offset]))
			
			// Handle event in goroutine
			go l.handleEvent(ctx, event)

			offset += int(event.Event_len)
		}
	}
}

func (l *Loader) handleEvent(ctx context.Context, event *unix.FanotifyEventMetadata) {
	defer func() {
		// Always respond to permission events
		response := unix.FanotifyResponse{
			Fd:       event.Fd,
			Response: unix.FAN_ALLOW,
		}
		responseBytes := (*(*[unsafe.Sizeof(response)]byte)(unsafe.Pointer(&response)))[:]
		unix.Write(l.fd, responseBytes)
		unix.Close(int(event.Fd))
	}()

	// Get file path
	path, err := l.getPath(int(event.Fd))
	if err != nil {
		logrus.WithError(err).Debug("failed to get path")
		return
	}

	// Skip if file already exists
	if _, err := os.Stat(path); err == nil {
		return
	}

	// Prevent duplicate loads
	l.pendingMu.Lock()
	mu, exists := l.pending[path]
	if !exists {
		mu = &sync.Mutex{}
		l.pending[path] = mu
	}
	l.pendingMu.Unlock()

	mu.Lock()
	defer func() {
		mu.Unlock()
		l.pendingMu.Lock()
		delete(l.pending, path)
		l.pendingMu.Unlock()
	}()

	// Load file on demand
	if err := l.loadFile(ctx, path); err != nil {
		logrus.WithError(err).Warnf("failed to load file %s", path)
	}
}

func (l *Loader) loadFile(ctx context.Context, path string) error {
	logrus.Debugf("lazy loading file: %s", path)
	
	// Check cache first
	if data, ok := l.cache.Get(path); ok {
		return os.WriteFile(path, data, 0644)
	}

	// In production, this would fetch from registry using range requests
	// For v2.0, we rely on prefetch and initial extraction
	logrus.Debugf("file %s not in cache, skipping lazy load", path)
	return nil
}

func (l *Loader) getPath(fd int) (string, error) {
	linkPath := fmt.Sprintf("/proc/self/fd/%d", fd)
	return os.Readlink(linkPath)
}